import { WebResult } from "src/common/web.result";
import * as crypto from "crypto";
import { Body, Controller, Get, Post } from "@nestjs/common";
import { WXSignatureDTO } from "./dto/wx-signature.dto";
import configuration from "src/common/configuration";

// "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"
// "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"
// "jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}"

let access_token = null;
let jsapi_ticket = null;

enum WXRequestErrorType {
  系统繁忙 = -1,
  请求成功 = 0,
  AppSecret错误 = 40001,
  AppSecret已被冻结 = 40243,
  不在白名单 = 400164,
}
class WXRequestError extends Error {
  public readonly errorCode: number | WXRequestErrorType;
  constructor(errorCode: number | WXRequestErrorType, message: string) {
    super(message);
    this.name = "WXRequestError";
    this.errorCode = errorCode;
  }
}

async function refreshAccessToken(): Promise<void> | never {
  const appid = configuration.wechat?.appID;
  const secret = configuration.wechat?.appSecret;
  if (!appid || !secret) {
    console.info("wx: appid or secret is empty");
    return;
  }
  const response = await fetch(
    `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`,
  ).then((response) => response.json());
  const result = response.data;
  if (result.errcode) {
    if (result.errcode === WXRequestErrorType.系统繁忙) {
      await refreshAccessToken();
      return;
    } else if (result.errcode !== WXRequestErrorType.请求成功) {
      throw new WXRequestError(result.errcode, result.errmsg);
    }
  }
  access_token = result.access_token;
}
async function refreshJSAPITicket(): Promise<void> | never {
  if (!access_token) {
    console.info("wx: access_token is empty");
    return;
  }
  const response = await fetch(
    `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=jsapi`,
  ).then((response) => response.json());
  const result = response.data;
  if (result.errcode) {
    if (result.errcode === WXRequestErrorType.系统繁忙) {
      await refreshJSAPITicket();
      return;
    } else if (result.errcode !== WXRequestErrorType.请求成功) {
      throw new WXRequestError(result.errcode, result.errmsg);
    }
  }
  jsapi_ticket = result.ticket;
}

@Controller("wx")
export class WXController {
  constructor() {
    fetch(`${configuration.website.url}/api/tool/x-real-ip`)
      .then((response) => response.json())
      .then((result) => {
        const ip = result.data;
        console.info("host ip:", ip);
      });

    // 没有 APPID 或 APPSecret 则不启用
    const appID = configuration.wechat?.appID;
    const appSecret = configuration.wechat?.appSecret;
    if (!appID || !appSecret) {
      return;
    }
    // 定时刷新
    async function refresh(): Promise<void> | never {
      await refreshAccessToken();
      await refreshJSAPITicket();
      console.info("wx access_token:", access_token);
      console.info("wx jsapi_ticket:", jsapi_ticket);
    }
    // 使用 setInterval 来循环调用刷新函数，同时在异常情况下正确处理定时器
    const ms = 1000 * 6000; // 6000 秒
    const timerID = setInterval(async () => {
      try {
        await refresh();
      } catch (e) {
        console.error(e);
        // 对特定错误进行处理
        if (
          e.errorCode === WXRequestErrorType.AppSecret错误 ||
          e.errorCode === WXRequestErrorType.AppSecret已被冻结 ||
          e.errorCode === WXRequestErrorType.不在白名单
        ) {
          clearInterval(timerID);
          access_token = null;
          jsapi_ticket = null;
        }
      }
    }, ms);
  }

  @Get(["/appid", "/appID"])
  getAPPID() {
    return WebResult.success(configuration.wechat?.appID);
  }

  @Post("/signature")
  signature(@Body() wxSignatureDTO: WXSignatureDTO) {
    if (!jsapi_ticket) {
      return WebResult.failure(undefined, "内部错误");
    }

    const timestamp = new Date().getTime();
    const noncestr = crypto.randomUUID();
    const string1 = `jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${wxSignatureDTO.url}`;
    const signature = crypto.createHash("sha1").update(string1).digest("hex");

    return WebResult.success({
      url: wxSignatureDTO.url,
      timestamp: timestamp,
      nonceStr: noncestr,
      signature: signature,
    });
  }
}
